
#include "unixhead.h"

#include <iostream>
#include <functional>

using std::cout;
using std::endl;
using std::string;

#include <nlohmann/json.hpp>
#include <workflow/WFFacilities.h>
#include <workflow/WFHttpServer.h>
#include <workflow/RedisMessage.h>

using Json = nlohmann::json;


class MuploadServer
{
public:
    MuploadServer(unsigned short port)
    : _port(port)
    , _server(std::bind(&MuploadServer::process, this, std::placeholders::_1))
    , _waitGroup(1)
    , _curServerTask(nullptr)
    {}

    void start()
    {
        if(_server.start(_port) == 0) {
            _waitGroup.wait();
            _server.stop();
        } else {
            cout << "server start failed" << endl;
        }
    }

    void process(WFHttpTask * serverTask);

private:
    void init(const string & name);
    void uppart(const string & uploadid, const string & idx);
    void complete(const string & uploadid);

private:
    unsigned short _port;
    WFHttpServer _server;
    WFFacilities::WaitGroup _waitGroup;
    WFHttpTask * _curServerTask;
};

void MuploadServer::process(WFHttpTask * serverTask)
{
    cout << "Muploadserver::process is running" << endl;
    _curServerTask = serverTask;
    //1.解析请求
    auto req = serverTask->get_req();
    string method = req->get_method();
    string uri = req->get_request_uri();
    string path = uri.substr(0, uri.find("?"));
    cout << "uri:" << uri << endl;
    cout << "method:" << method << endl;
    cout << "path:" << path << endl;

    if(method == "POST" && path == "/file/mupload/init") {
        ///file/mupload/init?username=lwh
        string nameKV = uri.substr(uri.find("?") + 1);
        string name = nameKV.substr(nameKV.find("=") + 1);
        init(name);
    } else if(method == "POST" && path == "/file/mupload/uppart") {
        //uppart
        ///file/mupload/uppart?uploadid=lwh2023:07:20-15:31:04&chunkidx=0
        string uploadIDKV = uri.substr(0, uri.find("&"));
        string uploadID = uploadIDKV.substr(uploadIDKV.find("=") + 1);
        string chunkidxKV = uri.substr(uri.find("&") + 1);
        string chunkidx = chunkidxKV.substr(chunkidxKV.find("=") + 1);
        cout << "uploadID: " << uploadID << endl;
        cout << "chunkidx: " << chunkidx << endl;
        uppart(uploadID, chunkidx);
    } else if(method == "GET" && path == "/file/mupload/complete") {
        //complete
        ///file/mupload/complete?uploadid=lwh2023:07:20-15:31:04
        string uploadID = uri.substr(uri.find("=") + 1);
        cout << "uploadID: " << uploadID << endl;
        complete(uploadID);
    }
}

void MuploadServer::init(const string & name)
{
    //1. 生成uploadid
    time_t tm = time(nullptr);
    struct tm * ptm = localtime(&tm);
    char buff[100] = {0};
    sprintf(buff, "%s%04d:%02d:%02d-%02d:%02d:%02d",
            name.c_str(),
            ptm->tm_year + 1900,
            ptm->tm_mon + 1,
            ptm->tm_mday,
            ptm->tm_hour,
            ptm->tm_min,
            ptm->tm_sec);
    string uploadID(buff);
    cout << "uploadID: " << uploadID << endl;

    //2. 获取消息体, 存的是JSON对象
    const void * body;
    size_t sz;
    auto req = _curServerTask->get_req();
    req->get_parsed_body(&body, &sz);

    Json fileInfo = Json::parse((const char *)body);
    cout << "fileInfo: \n" << fileInfo.dump(2) << endl;
    string filename = fileInfo["filename"];
    size_t filesize = fileInfo["filesize"];
    string filehash = fileInfo["filehash"];
    cout << "fliesize:" <<  filesize << endl;
    //3. 生成分片信息
    size_t chunksize = 1024 * 1024 * 100;
    size_t chunkcnt = filesize / chunksize + (filesize % chunksize > 0 ? 1 : 0);
    cout << "chunkcnt:" << chunkcnt << endl;

    //4. 构造JSON对象，发送给客户端
    fileInfo["chunksize"] = chunksize;
    fileInfo["chunkcnt"] = chunkcnt;
    fileInfo["uploadid"] = uploadID;
    auto resp = _curServerTask->get_resp();
    string msg = fileInfo.dump();
    resp->append_output_body(msg);
    resp->add_header_pair("Content-Type", "application/json");
    resp->add_header_pair("Content-Length", std::to_string(msg.size()));

    //5. 将uploadID对应的数据写入Redis中
    string redisURL = "redis://127.0.0.1:6379";
    auto redisTask = WFTaskFactory::create_redis_task(redisURL, 1, nullptr);
    redisTask->get_req()->set_request("HSET",
        {
            uploadID,
            "filename", filename,
            "filesize", std::to_string(filesize),
            "filehash", filehash,
            "chunksize", std::to_string(chunksize),
            "chunkcnt", std::to_string(chunkcnt)
        });
    series_of(_curServerTask)->push_back(redisTask);
}

void MuploadServer::uppart(const string & uploadid, const string & idx)
{
    //1. 创建以uploadid为名字的目录
    mkdir(uploadid.c_str(), 0755);
    //2. 在服务器本地将分片数据写入文件
    string chunkname = uploadid + "/" + idx;
    int fd = open(chunkname.c_str(), O_CREAT|O_RDWR, 0664);
    if(fd < 0) {
        perror("open");
        return;
    }
    const void * body;
    size_t sz;
    _curServerTask->get_req()->get_parsed_body(&body, &sz);
    cout << "body'size:" << sz << endl;
    int ret = write(fd, body, sz);
    cout << "write ret:" << ret << endl;
    close(fd);

    //3. 生成响应信息，通知客户端分片上传成功
    string msg("upload part success");
    _curServerTask->get_resp()->append_output_body(msg);
    _curServerTask->get_resp()->add_header_pair("Content-Type", "text/plain");
    _curServerTask->get_resp()->add_header_pair("Content-Length", std::to_string(msg.size()));

    //4. 分片信息写入Redis中
    string redisURL("redis://127.0.0.1:6379");
    auto redisTask = WFTaskFactory::create_redis_task(redisURL, 1, nullptr);
    redisTask->get_req()->set_request("HSET", {
        uploadid,
        "chunkidx_" + idx, "1"
    });
    series_of(_curServerTask)->push_back(redisTask);
}
    
void MuploadServer::complete(const string & uploadid)
{
    //1. 创建Redis任务进行验证
    string redisURL("redis://127.0.0.1:6379");
    auto redisTask = WFTaskFactory::create_redis_task(redisURL, 1, 
        [this](WFRedisTask * redistask){
            //错误的检测   
            int state = redistask->get_state();
            int error = redistask->get_error();
            if(state != WFT_STATE_SUCCESS) {
                printf("ocurrs error: %s\n", WFGlobal::get_error_string(state, error));
                return;
            }

            protocol::RedisValue value;
            redistask->get_resp()->get_result(value);
            if(value.is_array()) {
                cout << "value is array" << endl;
                size_t chunkcnt = 0;
                size_t hasUploadCnt = 0;
                for(size_t i = 0; i < value.arr_size(); i +=2) {
                    string key = value.arr_at(i).string_value();
                    string val = value.arr_at(i + 1).string_value();
                    if(key == "chunkcnt") {
                        chunkcnt = stoi(val);
                        cout << "chunkcnt:" << chunkcnt << endl;
                    }

                    string chunkidx("chunkidx_");
                    if(key.substr(0,chunkidx.size()) == chunkidx) {
                        ++hasUploadCnt;
                    }
                }

                if(chunkcnt == hasUploadCnt) {
                    //合并文件
                    //...
                    //验证通过, 通知客户端上传成功
                    string msg("upload file success");
                    this->_curServerTask->get_resp()->append_output_body(msg); 
                    this->_curServerTask->get_resp()->add_header_pair("Content-Type", "text/plain"); 
                    this->_curServerTask->get_resp()->add_header_pair("Content-Length", std::to_string(msg.size())); 
                } else {
                    //验证失败, 通知客户端上传失败
                    cout << "1111" << endl;
                    string msg("upload file failed, pls upload again");
                    this->_curServerTask->get_resp()->append_output_body(msg); 
                    this->_curServerTask->get_resp()->add_header_pair("Content-Type", "text/plain"); 
                    this->_curServerTask->get_resp()->add_header_pair("Content-Length", std::to_string(msg.size())); 
                }

            } else {
                //验证失败, 通知客户端上传失败
                cout << "2222" << endl;
                string msg("upload file failed, pls upload again");
                this->_curServerTask->get_resp()->append_output_body(msg); 
                this->_curServerTask->get_resp()->add_header_pair("Content-Type", "text/plain"); 
                this->_curServerTask->get_resp()->add_header_pair("Content-Length", std::to_string(msg.size())); 
            }
        });
    redisTask->get_req()->set_request("HGETALL", {uploadid});
    series_of(_curServerTask)->push_back(redisTask);
}

void test0()
{
    MuploadServer server(8888);
    server.start();
}


int main()
{
    test0();
    return 0;
}

